
module fcgi.tpl ;

import fcgi.jtype ;
import fcgi.Buffer;

static void loadObjects(T)(T* t,Dobject obj)  if(is(T==struct)){
	foreach(int i, c; t.tupleof) {
		alias typeof(c) _type ;
		const name	= T.tupleof[i].stringof[T.stringof.length + 3 .. $];
		t.tupleof[i]	= obj.Get( name ).toObject;
		assert(t.tupleof[i] !is null);
	}
}

struct Tpl_Factory {
	static const ClassName	= `Template` ;
	Program 	program ;
	char[]	tpl_src ;
	
	Dobject 	tpl_object ;
	Dobject 	tpl_create ;

	void Init(string file_path = `tpl.js` ){
		program	= new Program ;
		tpl_src	= cast(char[]) std.file.read(file_path);
		program.compile(file_path, assumeUnique(tpl_src), null);
		program.execute(null);
		
		Value* tmp_val = program.callcontext.global.Get(ClassName) ;
		if( tmp_val is null) {
			assert(false);
		}
		tpl_object	= tmp_val.toObject ;
		tmp_val = tpl_object.Get(`Create`) ;
		if( tmp_val is null) {
			assert(false);
		}
		DnativeFunction.init( tpl_object, tpl_fn_list , DontEnum | DontDelete | ReadOnly );
		tpl_create	=  tmp_val.toObject ;
	}
	
	Tpl create(string name , string path = null  ) {
		Value[3] args ;
		args[0].putVstring(name);
		if( path !is null ) {
			args[1].putVstring(path);
			args[2].putVstring( cast(string) std.file.read(path) );
		}
		Tpl tpl	= new Tpl (&this, name);
		tpl_create.Call(program.callcontext, program.callcontext.global, &tpl._tpl_value, args );
		tpl.Init ;
		return tpl ;
	}
	
	static vBuffer write_buffer;
	static NativeFunctionData[] tpl_fn_list = [{"tpl_buffer", &write_buffer_fn, 1 }];
	static void* write_buffer_fn(Dobject pthis, CallContext *cc, Dobject othis, Value* ret, Value[] arglist){
		if( write_buffer is null ) {
			assert(false);
			return  null ;
		}
		foreach(int i, ref v; arglist ) {
			if( v.isString ) {
				write_buffer( cast(void[]) v.toString );
			}else if( v.isNumber) {
				write_buffer( cast(void[]) v.toString );
			}else if( v.isBoolean ) {
				write_buffer( cast(void[]) v.toString );
			}else if( v.isUndefined ) {
				write_buffer( cast(void[]) `undefined`);
			}else if( v.isNull ){
				write_buffer( cast(void[])  `null`);
			} else {
				write_buffer( cast(void[]) v.toString );
			}
		}
		return null ;
	}
}


final class Tpl {
	
	Tpl_Factory*	_factory ;
	Value			_tpl_value ;
	Dobject		_tpl_object ;
	string			_tpl_name ;
	vBuffer 		_buffer ;
	
	static struct _G {
		Dobject	render ;
		Dobject	clear ;
		Dobject	tpl_vars ;
	}
	_G	_g ;
	
	private this(Tpl_Factory* factory, string name){
		_factory	= factory ;
		_tpl_name	= name ;
		_buffer	= new vBuffer( 1024 * 16, 1024 *64);
	}
	
	private void Init(){
		_tpl_object	= _tpl_value.toObject ;
		loadObjects(&_g, _tpl_object);
	}
	
	Dobject __get(string[] path = null) {
		Dobject _ret	= _factory.program.callcontext.global.Get( `tpl_instances` ).toObject.Get(_tpl_name).toObject ;
		if( path !is null ){
			foreach( int i , name; path ){
				auto val = _ret.Get(name);
				if( val is null ){
					break;
				}
				_ret	= val.toObject ;
			}
		}
		return _ret ;
	}
	
	typeof(this) assign(T)(T* v, string name ) if( is(T==struct) ){
		alias JType!(T) T_;
		T_.Init(_factory.program);
		_g.tpl_vars.Put(name,  new T_(v), 0 );
		return this;
	}
	
	typeof(this) assign(T)(T v, string name ) if( is(T==class) ){
		alias JType!(T) T_;
		T_.Init(_factory.program);
		_g.tpl_vars.Put(name,  new T_(v), 0 );
		return this;
	}
	
	typeof(this) assign(T)(in T[] list, string name ) if(is(T==struct)  || is(T==class) || (isPointer!(T) && is(pointerTarget!(T)==struct) ) ){
		static if( is(T==class) || is(T==struct)  ) {
			alias T T1;
		} else	static if( isPointer!(T) ) {
			alias pointerTarget!(T) T1;
		}
		alias JType!(T1) T_;
		T_.Init(_factory.program);
		
		Darray a = new Darray ;
		if( list !is null && list.length > 0 ){
			Value[] _list = new Value[ list.length] ;
			foreach(int i, ref v; list){
				static if( is(T==struct)){
					_list[i].putVobject( new T_.Obj(cast(T*)&v) ) ;
				} else static if( is(T==class) || isPointer!(T) ){
					if( v !is null) {
						_list[i].putVobject( new T_( cast(T)v) ) ;
					}
				}
				a.Put(i, &_list[i], 0);
			}
		}
		_g.tpl_vars.Put(name, a, 0 );
		return this;
	}
	
	typeof(this) assign(T)(T[] list, string name ) if( isSomeString!(T) || isNumeric!(T) || is(T==bool) ){
		Darray a = new Darray ;
		if( list !is null && list.length > 0 ){
			Value[] _list = new Value[ list.length] ;
			foreach(int i, ref v; list){
				static if(isSomeString!(T)){
					_list[i].putVstring(v);
				} else static if(isNumeric!(T)){
					_list[i].putVnumber(v);
				} else static if( is(T==bool)){
					_list[i].putVbool(v);
				}else{
					static assert(false);
				}
				a.Put(i, &_list[i], 0);
			}
		}
		_g.tpl_vars.Put(name, a, 0 );
		return this;
	}
	
	typeof(this) assign(T, K)(T[K] list, string name ) if( ( isSomeString!(K)  || isNumeric!(T) ) && (  isSomeString!(K)  || isNumeric!(T) ||  is(T==bool) || is(T==class) || is(T==struct) || ( isPointer!(T) && is(pointerTarget!(T)==struct) ) )  ){

		static if(  is(T==class) || is(T==struct) || ( isPointer!(T) && is(pointerTarget!(T)==struct) ) ){
			static if( is(T==class) ){
				alias T Base;
				alias T pBase;
			} else static if(is(T==struct)){
				alias T Base;
				alias T* pBase;
			} else {
				alias pointerTarget!(T) Base;
				alias T pBase;
			}
			alias JType!(T1) _JType;
			_JType.Init(_factory.program);
		}
		
		Dobject o = new Dobject( Dobject.getPrototype() ) ;
		if( list !is null && list.length > 0 ){
			Value[] _list = new Value[ list.length] ;
			Value[] _keys = new Value[ list.length] ;
			int i	= 0;
			foreach(K k, ref T v; list){
				static if(isSomeString!(T)){
					_list[i].putVstring(v);
				} else static if(isNumeric!(T)){
					_list[i].putVnumber(v);
				} else static if( is(T==bool)){
					_list[i].putVbool(v);
				}else static if(isPointer!(T)){
					_list[i].putVobject( new _JType.Obj(v) );
				} else static if( is(T==struct) ){
					_list[i].putVobject( new _JType.Obj( &v) );
				} else {
					static assert(false);
				}
				_list[i].putVstring(v);
				
				static if(isSomeString!(K)){
					o.Put(k, &_list[i], 0);
				} else {
					static assert(false);
				}
				i++;
			}
		}
		_g.tpl_vars.Put(name, o, 0 );
		return this;
	}
	
	typeof(this) render(string path = null ){
		Value[1] args;
		args[0].putVstring(path);
		Value ret;
		vBuffer tmp_buffer	= Tpl_Factory.write_buffer ;
		Tpl_Factory.write_buffer = _buffer ;
		_buffer.clear;
		_g.render.Call(_factory.program.callcontext,  _tpl_object , &ret, args );
		 Tpl_Factory.write_buffer = tmp_buffer ;
		return this;
	}
	
	typeof(this) clear(){
		_g.clear.Call(_factory.program.callcontext,  _tpl_object , null, null );
		return this ;
	}
	
	string buffer(){
		return cast(string) _buffer.slice ;
	}
}